package com.chenfan.mcn.extension.log;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.chenfan.mcn.config.UserVoContextHolder;
import com.chenfan.mcn.constant.SeparatorCst;
import com.chenfan.common.extension.dict.DictTranslator;
import com.chenfan.mcn.component.dict.DataDictTranslator;
import com.chenfan.mcn.utils.CommonUtil;
import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author tangwei
 * Date:     2021/1/10 9:40
 * Description: 将表字段变化转换为日志记录
 */
@Slf4j
public class FieldChangeUtil {

    private static Map<Class<? extends DictTranslator>, DictTranslator> translatorMap = new ConcurrentHashMap(64);

    private static DataDictTranslator dataDictTranslator = new DataDictTranslator();

    /**
     * 比较对象各字段前后差异
     * @param fieldChangeDTO
     * @param <T>
     * @return
     */
    public static <T> List<FieldChangeOperateLog> contrast(FieldChangeDTO<T> fieldChangeDTO) {
        if(OperationTypeEnum.isDelete(fieldChangeDTO.getOperationType())) {
            return Lists.newArrayList(buildFieldChangeOperateLog(fieldChangeDTO, FieldTypeEnum.STRING.name(), "是否删除", "否", "是"));
        } else if(OperationTypeEnum.isInsert(fieldChangeDTO.getOperationType())) {
            return Lists.newArrayList(buildFieldChangeOperateLog(fieldChangeDTO, FieldTypeEnum.STRING.name(), "全部", null, null));
        }
        T beforeValue = fieldChangeDTO.getBeforeValue();
        T afterValue = fieldChangeDTO.getAfterValue();
        if(beforeValue == null || afterValue == null) {
            return new ArrayList<>(0);
        }
        List<FieldChangeOperateLog> fieldChangeOperateLogs = new ArrayList<>();
        /** 通过被转换实体类class文件，获取字段名称 */
        List<Field> afterFields = Stream.of(CommonUtil.getAllDeclaredFields(afterValue.getClass())).filter(e -> e.isAnnotationPresent(FieldDesc.class)).collect(Collectors.toList());
        if(CollectionUtils.isNotEmpty(afterFields)) {
            Map<FieldDesc, ImmutablePair<String, String>> dictMap = new HashMap<>(afterFields.size());
            /** 遍历字段，比较每个字段变化前后差异 */
            for (Field field : afterFields) {
                FieldDesc fieldDesc = field.getAnnotation(FieldDesc.class);
                try {
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), afterValue.getClass());
                    Method readMethod = pd.getReadMethod();
                    Object before = readMethod.invoke(beforeValue);
                    Object after = readMethod.invoke(afterValue);
                    if (equals(fieldDesc.isArray(), before, after)) {
                        continue;
                    }
                    if(StringUtils.isNotBlank(fieldDesc.dictType())) {
                        dictMap.put(fieldDesc, new ImmutablePair<>(Objects.nonNull(before) ? before.toString() : "", Objects.nonNull(after) ? after.toString() : ""));
                        continue;
                    }
                    //可以优化下做数组翻译
                    String beforeString = convertToString(fieldDesc, before);
                    String afterString = convertToString(fieldDesc, after);
                    fieldChangeOperateLogs.add(buildFieldChangeOperateLog(fieldChangeDTO, fieldDesc.fieldType().name(), fieldDesc.value(), beforeString, afterString));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
            addDefaultDictLogs(fieldChangeDTO, fieldChangeOperateLogs, dictMap);
        }
        return fieldChangeOperateLogs;
    }

    /**
     * 转成字符串
     * @param value
     * @return
     */
    private static String convertToString(FieldDesc fieldDesc, Object value) {
        String format = fieldDesc.format();
        String[] replace = fieldDesc.replace();
        Class<?> translator = fieldDesc.translator();
        if(Objects.isNull(value)) {
            return "";
        } else if (value instanceof Boolean) {
            /** 布尔值类型字段日志值转换 */
            return ((Boolean)value).booleanValue() ? "是" : "否";
        } else if (value instanceof Date) {
            /** Date类型字段日志值转换 */
            return DateUtil.format((Date) value, StringUtils.defaultIfBlank(format, "yyyy-MM-dd HH:mm:ss"));
        } else if (value instanceof LocalDateTime) {
            /** LocalDateTime类型字段日志值转换 */
            return LocalDateTimeUtil.format((LocalDateTime) value, StringUtils.defaultIfBlank(format, "yyyy-MM-dd HH:mm:ss"));
        } else if (value instanceof LocalDate) {
            /** LocalDate类型字段日志值转换 */
            return LocalDateTimeUtil.format((LocalDate) value, StringUtils.defaultIfBlank(format, "yyyy-MM-dd"));
        } else if (value instanceof BigDecimal) {
            /** BigDecimal类型字段日志值转换 */
            DecimalFormat df = new DecimalFormat(StringUtils.defaultIfBlank(format, "###.##"));
            return df.format(new BigDecimal(value.toString()));
        } else if (ArrayUtils.isNotEmpty(replace)) {
            return replaceValue(replace, value.toString());
        } else if(value instanceof String && StringUtils.isNotBlank(format)) {
            if(StringUtils.isNotBlank(value.toString())) {
                return String.format(format, value);
            }
        }else if (translator != null && DictTranslator.class.isAssignableFrom(translator)) {
            if(!translatorMap.containsKey(translator)) {
                initTranslator((Class<? extends DictTranslator>)translator);
            }
            if(translatorMap.containsKey(translator)) {
                Object translateValue = translatorMap.get(translator).translate(value);
                return Objects.nonNull(translateValue) ? translateValue.toString() : "";
            }
        }
        return StringUtils.trim(value.toString());
    }

    /**
     * 初始化Translator
     * @param translator
     */
    @SneakyThrows
    private static synchronized void initTranslator(Class<? extends DictTranslator> translator) {
        if(translatorMap.containsKey(translator)) {
            return;
        }
        if(translator.isEnum()) {
            Optional<? extends Enum> optional = EnumSet.allOf((Class<? extends Enum>)translator).stream().findFirst();
            if(optional.isPresent()) {
                translatorMap.putIfAbsent(translator, (DictTranslator)optional.get());
            }
        } else {
            translatorMap.putIfAbsent(translator, translator.newInstance());
        }
    }

    /**
     * 判断是否相等
     * @param isArray
     * @param before
     * @param after
     * @return
     */
    private static boolean equals(boolean isArray, Object before, Object after) {
        if (CommonUtil.isBlankIfStr(isArray, before) && CommonUtil.isBlankIfStr(isArray, after)) {
            return true;
        } else if(Objects.nonNull(before) && Objects.nonNull(after) ) {
            return before instanceof BigDecimal ? ((BigDecimal) before).compareTo((BigDecimal)after) == 0 : before.equals(after);
        } else {
            return false;
        }
    }

    /**
     * 替换值
     * @param replace
     * @param value
     * @return
     */
    private static String replaceValue(String[] replace, String value) {
        String[] temp;
        for (String str : replace) {
            temp = str.split("_");
            if (value.equals(temp[1])) {
                value = temp[0];
                break;
            }
        }
        return value;
    }

    /**
     * 构建操作日志
     * @param fieldChangeDTO
     * @param fieldType
     * @param fieldName
     * @param beforeString
     * @param afterString
     * @param <T>
     * @return
     */
    private static <T> FieldChangeOperateLog buildFieldChangeOperateLog(FieldChangeDTO<T> fieldChangeDTO, String fieldType, String fieldName, String beforeString, String afterString) {
        FieldChangeOperateLog fieldChangeOperateLog = new FieldChangeOperateLog();
        fieldChangeOperateLog.setTraceId(fieldChangeDTO.getTraceId());
        fieldChangeOperateLog.setActionName(fieldChangeDTO.getActionName());
        fieldChangeOperateLog.setBussinessId(fieldChangeDTO.getBusinessId());
        fieldChangeOperateLog.setBussinessType(fieldChangeDTO.getBusinessType());
        fieldChangeOperateLog.setRemark(fieldChangeDTO.getRemark());
        fieldChangeOperateLog.setOperationType(fieldChangeDTO.getOperationType());
        fieldChangeOperateLog.setFieldType(fieldType);
        fieldChangeOperateLog.setBeforeValue(beforeString);
        fieldChangeOperateLog.setAfterValue(afterString);
        fieldChangeOperateLog.setOperationField(fieldName);
        fieldChangeOperateLog.setOperationBy(ObjectUtils.defaultIfNull(fieldChangeDTO.getOperationBy(), UserVoContextHolder.getUserId()));
        fieldChangeOperateLog.setOperationName(ObjectUtils.defaultIfNull(fieldChangeDTO.getOperationName(), UserVoContextHolder.getUserName()));
        fieldChangeOperateLog.setOperationTime(fieldChangeDTO.getOperationTime());
        return fieldChangeOperateLog;
    }

    /**
     * 添加默认字典日志
     * @param fieldChangeDTO
     * @param fieldChangeOperateLogs
     * @param dictMap
     * @param <T>
     */
    private static <T> void addDefaultDictLogs(FieldChangeDTO<T> fieldChangeDTO, List<FieldChangeOperateLog> fieldChangeOperateLogs, Map<FieldDesc, ImmutablePair<String, String>> dictMap) {
        if(MapUtils.isEmpty(dictMap)) {
            return;
        }
        Map<String, String> translatedMap = dataDictTranslator.translate(dictMap);
        dictMap.entrySet().forEach(e -> {
            FieldDesc fieldDesc = e.getKey();
            ImmutablePair<String, String> immutablePair = e.getValue();
            FieldChangeOperateLog fieldChangeOperateLog = new FieldChangeOperateLog();
            fieldChangeOperateLog.setTraceId(fieldChangeDTO.getTraceId());
            fieldChangeOperateLog.setActionName(fieldChangeDTO.getActionName());
            fieldChangeOperateLog.setBussinessId(fieldChangeDTO.getBusinessId());
            fieldChangeOperateLog.setBussinessType(fieldChangeDTO.getBusinessType());
            fieldChangeOperateLog.setRemark(fieldChangeDTO.getRemark());
            fieldChangeOperateLog.setOperationType(fieldChangeDTO.getOperationType());
            fieldChangeOperateLog.setFieldType(fieldDesc.fieldType().name());
            if(StringUtils.isBlank(immutablePair.getLeft())) {
                fieldChangeOperateLog.setBeforeValue("");
            } else {
                fieldChangeOperateLog.setBeforeValue(translatedMap.get(StringUtils.joinWith(SeparatorCst.SEMICOLON, fieldDesc.dictType(), immutablePair.getLeft())));
            }
            if(StringUtils.isBlank(immutablePair.getRight())) {
                fieldChangeOperateLog.setAfterValue("");
            } else {
                fieldChangeOperateLog.setAfterValue(translatedMap.get(StringUtils.joinWith(SeparatorCst.SEMICOLON, fieldDesc.dictType(), immutablePair.getRight())));
            }
            fieldChangeOperateLog.setOperationField(fieldDesc.value());
            fieldChangeOperateLog.setOperationBy(ObjectUtils.defaultIfNull(fieldChangeDTO.getOperationBy(), UserVoContextHolder.getUserId()));
            fieldChangeOperateLog.setOperationName(ObjectUtils.defaultIfNull(fieldChangeDTO.getOperationName(), UserVoContextHolder.getUserName()));
            fieldChangeOperateLog.setOperationTime(fieldChangeDTO.getOperationTime());
            fieldChangeOperateLogs.add(fieldChangeOperateLog);
        });
    }
}